*
* Event channels between domains.
*
- * Copyright (c) 2003, K A Fraser.
+ * Copyright (c) 2003-2004, K A Fraser.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
#include <xeno/sched.h>
#include <xeno/event.h>
+#include <hypervisor-ifs/hypervisor-if.h>
+#include <hypervisor-ifs/event_channel.h>
+
#define MAX_EVENT_CHANNELS 1024
-static long event_channel_open(u16 target_dom)
+static int get_free_port(struct task_struct *p)
{
- struct task_struct *lp = current, *rp;
- int i, lmax, rmax, lid, rid;
- event_channel_t *lchn, *rchn;
- shared_info_t *rsi;
+ int max, port;
+ event_channel_t *chn;
+
+ max = p->max_event_channel;
+ chn = p->event_channel;
+
+ for ( port = 0; port < max; port++ )
+ if ( chn[port].state == ECS_FREE )
+ break;
+
+ if ( port == max )
+ {
+ if ( max == MAX_EVENT_CHANNELS )
+ return -ENOSPC;
+
+ max = (max == 0) ? 4 : (max * 2);
+
+ chn = kmalloc(max * sizeof(event_channel_t), GFP_KERNEL);
+ if ( unlikely(chn == NULL) )
+ return -ENOMEM;
+
+ memset(chn, 0, max * sizeof(event_channel_t));
+
+ if ( p->event_channel != NULL )
+ {
+ memcpy(chn, p->event_channel, (max/2) * sizeof(event_channel_t));
+ kfree(p->event_channel);
+ }
+
+ p->event_channel = chn;
+ p->max_event_channel = max;
+ }
+
+ return port;
+}
+
+static inline unsigned long set_event_pending(struct task_struct *p, int port)
+{
+ if ( !test_and_set_bit(port, &p->shared_info->event_channel_pend[0]) &&
+ !test_and_set_bit(port>>5, &p->shared_info->event_channel_pend_sel) )
+ return mark_guest_event(p, _EVENT_EVTCHN);
+ return 0;
+}
+
+static inline unsigned long set_event_disc(struct task_struct *p, int port)
+{
+ if ( !test_and_set_bit(port, &p->shared_info->event_channel_disc[0]) &&
+ !test_and_set_bit(port>>5, &p->shared_info->event_channel_disc_sel) )
+ return mark_guest_event(p, _EVENT_EVTCHN);
+ return 0;
+}
+
+static long event_channel_open(evtchn_open_t *open)
+{
+ struct task_struct *lp, *rp;
+ int lport = 0, rport = 0;
unsigned long cpu_mask;
+ domid_t ldom = open->local_dom, rdom = open->remote_dom;
long rc = 0;
- rp = find_domain_by_id(target_dom);
+ if ( !IS_PRIV(current) )
+ return -EPERM;
+
+ /* 'local_dom' may be DOMID_SELF. 'remote_dom' cannot be.*/
+ if ( ldom == DOMID_SELF )
+ ldom = current->domain;
- /*
- * We need locks at both ends to make a connection. We avoid deadlock
- * by acquiring the locks in address order.
- */
- if ( (unsigned long)lp < (unsigned long)rp )
+ /* Event channel must connect distinct domains. */
+ if ( ldom == rdom )
+ return -EINVAL;
+
+ if ( ((lp = find_domain_by_id(ldom)) == NULL) ||
+ ((rp = find_domain_by_id(rdom)) == NULL) )
+ {
+ if ( lp != NULL )
+ put_task_struct(lp);
+ return -ESRCH;
+ }
+
+ /* Avoid deadlock by first acquiring lock of domain with smaller id. */
+ if ( ldom < rdom )
{
spin_lock(&lp->event_channel_lock);
spin_lock(&rp->event_channel_lock);
}
else
{
- if ( likely(rp != NULL) )
- spin_lock(&rp->event_channel_lock);
+ spin_lock(&rp->event_channel_lock);
spin_lock(&lp->event_channel_lock);
}
- lmax = lp->max_event_channel;
- lchn = lp->event_channel;
- lid = -1;
-
- /*
- * Find the first unused event channel. Also ensure bo channel already
- * exists to the specified target domain.
- */
- for ( i = 0; i < lmax; i++ )
+ if ( (lport = get_free_port(lp)) < 0 )
{
- if ( (lid == -1) && !(lchn[i].flags & ECF_INUSE) )
- {
- lid = i;
- }
- else if ( unlikely(lchn[i].target_dom == target_dom) )
- {
- rc = -EEXIST;
- goto out;
- }
+ rc = lport;
+ goto out;
}
-
- /* If there is no free slot we need to allocate a bigger channel list. */
- if ( unlikely(lid == -1) )
- {
- /* Reached maximum channel count? */
- if ( unlikely(lmax == MAX_EVENT_CHANNELS) )
- {
- rc = -ENOSPC;
- goto out;
- }
-
- lmax = (lmax == 0) ? 4 : (lmax * 2);
-
- lchn = kmalloc(lmax * sizeof(event_channel_t), GFP_KERNEL);
- if ( unlikely(lchn == NULL) )
- {
- rc = -ENOMEM;
- goto out;
- }
- memset(lchn, 0, lmax * sizeof(event_channel_t));
-
- if ( likely(lp->event_channel != NULL) )
- kfree(lp->event_channel);
-
- lp->event_channel = lchn;
- lp->max_event_channel = lmax;
+ if ( (rport = get_free_port(rp)) < 0 )
+ {
+ rc = rport;
+ goto out;
}
- lchn[lid].target_dom = target_dom;
- lchn[lid].flags = ECF_INUSE;
+ lp->event_channel[lport].remote_dom = rp;
+ lp->event_channel[lport].remote_port = (u16)rport;
+ lp->event_channel[lport].state = ECS_CONNECTED;
- if ( likely(rp != NULL) )
- {
- rchn = rp->event_channel;
- rmax = rp->max_event_channel;
-
- for ( rid = 0; rid < rmax; rid++ )
- {
- if ( (rchn[rid].target_dom == lp->domain) &&
- (rchn[rid].flags & ECF_INUSE) )
- {
- /*
- * The target was awaiting a connection. We make the connection
- * and send a connection-made event to the remote end.
- */
- rchn[rid].flags = ECF_INUSE | ECF_CONNECTED | lid;
- lchn[lid].flags = ECF_INUSE | ECF_CONNECTED | rid;
-
- rsi = rp->shared_info;
- if ( !test_and_set_bit(rid, &rsi->event_channel_pend[0]) &&
- !test_and_set_bit(rid>>5, &rsi->event_channel_pend_sel) )
- {
- cpu_mask = mark_guest_event(rp, _EVENT_EVTCHN);
- guest_event_notify(cpu_mask);
- }
-
- break;
- }
- }
- }
+ rp->event_channel[rport].remote_dom = lp;
+ rp->event_channel[rport].remote_port = (u16)lport;
+ rp->event_channel[rport].state = ECS_CONNECTED;
+
+ cpu_mask = set_event_pending(lp, lport);
+ cpu_mask |= set_event_pending(rp, rport);
+ guest_event_notify(cpu_mask);
out:
spin_unlock(&lp->event_channel_lock);
- if ( rp != NULL )
- {
- spin_unlock(&rp->event_channel_lock);
- put_task_struct(rp);
- }
+ spin_unlock(&rp->event_channel_lock);
+
+ put_task_struct(lp);
+ put_task_struct(rp);
+
+ open->local_port = lport;
+ open->remote_port = rport;
return rc;
}
-static long event_channel_close(u16 lid)
+static long __event_channel_close(struct task_struct *lp, int lport)
{
- struct task_struct *lp = current, *rp = NULL;
+ struct task_struct *rp = NULL;
event_channel_t *lchn, *rchn;
- u16 rid;
- shared_info_t *rsi;
+ int rport;
unsigned long cpu_mask;
long rc = 0;
lchn = lp->event_channel;
- if ( unlikely(lid >= lp->max_event_channel) ||
- unlikely(!(lchn[lid].flags & ECF_INUSE)) )
+ if ( (lport < 0) || (lport >= lp->max_event_channel) ||
+ (lchn[lport].state == ECS_FREE) )
{
rc = -EINVAL;
goto out;
}
- if ( lchn[lid].flags & ECF_CONNECTED )
+ if ( lchn[lport].state == ECS_CONNECTED )
{
if ( rp == NULL )
{
- rp = find_domain_by_id(lchn[lid].target_dom);
- ASSERT(rp != NULL);
-
- if ( (unsigned long)lp < (unsigned long)rp )
+ rp = lchn[lport].remote_dom;
+ get_task_struct(rp);
+
+ if ( lp->domain < rp->domain )
{
spin_lock(&rp->event_channel_lock);
}
goto again;
}
}
- else if ( rp->domain != lchn[lid].target_dom )
+ else if ( rp != lchn[lport].remote_dom )
{
rc = -EINVAL;
goto out;
}
- rchn = rp->event_channel;
- rid = lchn[lid].flags & ECF_TARGET_ID;
- ASSERT(rid < rp->max_event_channel);
- ASSERT(rchn[rid].flags == (ECF_INUSE | ECF_CONNECTED | lid));
- ASSERT(rchn[rid].target_dom == lp->domain);
-
- rchn[rid].flags = ECF_INUSE;
-
- rsi = rp->shared_info;
- if ( !test_and_set_bit(rid, &rsi->event_channel_disc[0]) &&
- !test_and_set_bit(rid>>5, &rsi->event_channel_disc_sel) )
- {
- cpu_mask = mark_guest_event(rp, _EVENT_EVTCHN);
- guest_event_notify(cpu_mask);
- }
+ rchn = rp->event_channel;
+ rport = lchn[lport].remote_port;
+
+ if ( rport >= rp->max_event_channel )
+ BUG();
+ if ( rchn[rport].state != ECS_CONNECTED )
+ BUG();
+ if ( rchn[rport].remote_dom != lp )
+ BUG();
+
+ rchn[rport].state = ECS_ZOMBIE;
+ rchn[rport].remote_dom = NULL;
+ rchn[rport].remote_port = 0xFFFF;
+
+ cpu_mask = set_event_disc(lp, lport);
+ cpu_mask |= set_event_disc(rp, rport);
+ guest_event_notify(cpu_mask);
}
- lchn[lid].target_dom = 0;
- lchn[lid].flags = 0;
+ lchn[lport].state = ECS_FREE;
+ lchn[lport].remote_dom = NULL;
+ lchn[lport].remote_port = 0xFFFF;
out:
spin_unlock(&lp->event_channel_lock);
+ put_task_struct(lp);
+
if ( rp != NULL )
{
spin_unlock(&rp->event_channel_lock);
}
-static long event_channel_send(u16 lid)
+static long event_channel_close(evtchn_close_t *close)
+{
+ struct task_struct *lp;
+ int lport = close->local_port;
+ long rc;
+ domid_t ldom = close->local_dom;
+
+ if ( ldom == DOMID_SELF )
+ ldom = current->domain;
+ else if ( !IS_PRIV(current) )
+ return -EPERM;
+
+ if ( (lp = find_domain_by_id(ldom)) == NULL )
+ return -ESRCH;
+
+ rc = __event_channel_close(lp, lport);
+
+ put_task_struct(lp);
+ return rc;
+}
+
+
+static long event_channel_send(int lport)
{
struct task_struct *lp = current, *rp;
- u16 rid, rdom;
- shared_info_t *rsi;
+ int rport;
unsigned long cpu_mask;
spin_lock(&lp->event_channel_lock);
- if ( unlikely(lid >= lp->max_event_channel) ||
- unlikely(!(lp->event_channel[lid].flags & ECF_CONNECTED)) )
+ if ( unlikely(lport < 0) ||
+ unlikely(lport >= lp->max_event_channel) ||
+ unlikely(lp->event_channel[lport].state != ECS_CONNECTED) )
{
spin_unlock(&lp->event_channel_lock);
return -EINVAL;
}
- rdom = lp->event_channel[lid].target_dom;
- rid = lp->event_channel[lid].flags & ECF_TARGET_ID;
+ rp = lp->event_channel[lport].remote_dom;
+ rport = lp->event_channel[lport].remote_port;
- spin_unlock(&lp->event_channel_lock);
+ get_task_struct(rp);
- if ( unlikely(rid >= MAX_EVENT_CHANNELS) ||
- unlikely ((rp = find_domain_by_id(rdom)) == NULL) )
- return -EINVAL;
+ spin_unlock(&lp->event_channel_lock);
- rsi = rp->shared_info;
- if ( !test_and_set_bit(rid, &rsi->event_channel_pend[0]) &&
- !test_and_set_bit(rid>>5, &rsi->event_channel_pend_sel) )
- {
- cpu_mask = mark_guest_event(rp, _EVENT_EVTCHN);
- guest_event_notify(cpu_mask);
- }
+ cpu_mask = set_event_pending(rp, rport);
+ guest_event_notify(cpu_mask);
put_task_struct(rp);
+
return 0;
}
-static long event_channel_status(u16 lid)
+static long event_channel_status(evtchn_status_t *status)
{
- struct task_struct *lp = current;
+ struct task_struct *lp;
+ domid_t ldom = status->local_dom;
+ int lport = status->local_port;
event_channel_t *lchn;
- long rc = EVTCHNSTAT_closed;
+
+ if ( ldom == DOMID_SELF )
+ ldom = current->domain;
+ else if ( !IS_PRIV(current) )
+ return -EPERM;
+
+ if ( (lp = find_domain_by_id(ldom)) == NULL )
+ return -ESRCH;
spin_lock(&lp->event_channel_lock);
lchn = lp->event_channel;
- if ( lid < lp->max_event_channel )
+ if ( (lport < 0) || (lport >= lp->max_event_channel) )
{
- if ( lchn[lid].flags & ECF_CONNECTED )
- rc = EVTCHNSTAT_connected;
- else if ( lchn[lid].flags & ECF_INUSE )
- rc = EVTCHNSTAT_disconnected;
+ spin_unlock(&lp->event_channel_lock);
+ return -EINVAL;
+ }
+
+ switch ( lchn[lport].state )
+ {
+ case ECS_FREE:
+ status->status = EVTCHNSTAT_closed;
+ break;
+ case ECS_ZOMBIE:
+ status->status = EVTCHNSTAT_disconnected;
+ break;
+ case ECS_CONNECTED:
+ status->status = EVTCHNSTAT_connected;
+ status->remote_dom = lchn[lport].remote_dom->domain;
+ status->remote_port = lchn[lport].remote_port;
+ break;
+ default:
+ BUG();
}
spin_unlock(&lp->event_channel_lock);
- return rc;
+ return 0;
}
-long do_event_channel_op(unsigned int cmd, unsigned int id)
+long do_event_channel_op(evtchn_op_t *uop)
{
long rc;
+ evtchn_op_t op;
+
+ if ( copy_from_user(&op, uop, sizeof(op)) != 0 )
+ return -EFAULT;
- switch ( cmd )
+ switch ( op.cmd )
{
case EVTCHNOP_open:
- rc = event_channel_open((u16)id);
+ rc = event_channel_open(&op.u.open);
+ if ( copy_to_user(uop, &op, sizeof(op)) != 0 )
+ rc = -EFAULT; /* Cleaning up here would be a mess! */
break;
case EVTCHNOP_close:
- rc = event_channel_close((u16)id);
+ rc = event_channel_close(&op.u.close);
break;
case EVTCHNOP_send:
- rc = event_channel_send((u16)id);
+ rc = event_channel_send(op.u.send.local_port);
break;
case EVTCHNOP_status:
- rc = event_channel_status((u16)id);
+ rc = event_channel_status(&op.u.status);
+ if ( copy_to_user(uop, &op, sizeof(op)) != 0 )
+ rc = -EFAULT;
break;
default:
if ( p->event_channel != NULL )
{
for ( i = 0; i < p->max_event_channel; i++ )
- (void)event_channel_close((u16)i);
+ (void)__event_channel_close(p, i);
kfree(p->event_channel);
}
}